/**
 * @author W
 * @date 2020/4/6 19:17
 */

// 映射类型：TypeScript提供了从旧类型中创建新类型的一种方式 — 映射类型。 在映射类型里，新类型以相同的形式去转换旧类型里每个属性。

// 将旧类型中所有的属性改为只读属性
{
    interface People {
        name: string;
        age: number;

        run(): void;
    }

    // 使用类型别名,并且使用泛型制定更通用的映射类型模板
    type ReadonlyPeople<T> = {
        readonly [P in keyof T]: T[P]
        // 它的语法与索引签名的语法类型，内部使用了 for .. in。 具有三个部分：
        // 类型变量 P，它会依次绑定到每个属性。
        // keyof T 表示字符串字面量联合的 Keys，它包含了要迭代的属性名的集合。
        // T[P] 属性的结果类型。
    }

    type newPeople = ReadonlyPeople<People>

    let p: newPeople = {
        name: 'wm',
        age: 22,
        run(): void {
            console.log('run...')
        }
    }

    // p.name = 'liuhui'  // error 只读属性不能更改
}

// 将旧类型中所有的属性改为可选属性
{
    interface People {
        name: string;
        age: number;

        run(): void;
    }

    type PartialPeople<T> = {
        [P in keyof T]?: T[P]
    }

    type newPeople = PartialPeople<People>

    // 所有属性都是可选属性
    let p: newPeople = {
        name: 'wang.mao',
    }
}

// 以上类型转换是 同态的，映射只作用于 T的属性而没有增加其它的。
// 编译器知道在添加任何新属性之前可以拷贝所有存在的属性修饰符。 例如，假设 Person.name是只读的，那么 Partial<Person>.name也将是只读的且为可选的。
{
    interface Person {
        readonly name: string,
        age: number,
        grade: number
    }

    type PartialPerson<T> = {
        [P in keyof T]?: T[P]
    }

    type newPerson = PartialPerson<Person>

    let p:newPerson= {
        name:'wm'
    }

    // p.name = 'liu.nan' // error name属性是只读的


    let p2:newPerson = { // 不用定义name和grade属性，因为是可选的
        age:22
    }
}

// 下面是另一个例子，使用映射类型为源类型设置getter、setter代理，就不是上面的同态类型
{
    // 定义代理的操作方法，有get、set函数，并且都是指定的T类型
    type Handle<T> = {
        get():T;
        set(value:T):boolean;
    }

    // 定义代理映射类型，为每个属性绑定get、set方法
    type Proxy<T> = {
        [P in keyof T]:Handle<T[P]>
    }

    interface Person {
        name: string,
    }

    type newPerson = Proxy<Person>

    // 要为name属性创建get、set方法
    let p:newPerson = {
        name:{
            get(){
                return 'wm'
            },
            set(val){
                return !!val
            }
        }
    }

    console.log(p.name)
}

// 给源对象上的每个属性定义个新类型
{
    // K继承类型string，拥有string上的属性和方法
    // type Record<K extends string, T> = {
    //     [P in K]:T  // 对象上的每个属性定义个新类型 T
    // }

    interface Person {
        name: string,
        readonly age: number,
        grade: number
    }

    // keyof Person 返回Person上每个字符串属性组成的联合类型： 'name' | 'age' | 'grade'
    type newPerson = Record<keyof Person,boolean> // 指定新类型为：boolean

    let p:newPerson= {
        // name:'wm',  // error  每个属性为布尔值
        name:true,
        age:false,
        grade:true
    }
    p.age = true  // TODO 按道理这里不会报错 因为 ：非同态类型本质上会创建新的属性，因此它们不会拷贝属性已有的修饰符，所以没有readonly属性。这里不知道为什么会报错
    console.log(p)
}

// Pick 选取以K为属性的对象T的子集
{
    // K是继承T的字面量类型，遍历K，返回K上属性的属性值类型
    type Pick<K extends keyof T,T> = {
      [P in K]:T[P]
    }

    interface Person {
        name: string,
        readonly age: number,
        grade: number
    }

    // 由Person上字符串属性组成的联合类型： 'name' | 'age'
    type newPerson = Pick<"name" | "age", Person> // 仅指定：'name' | 'age' 属性在Person上的属性值类型组成的对象

    let p:newPerson= {
        name:'wang.mao',
        age:12,
        // grade:12 //  报错 没有指定grade属性及其类型
    }
    console.log(p)
}
